﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Urbania.Tiles;
using Urbania.Utilities;

namespace Urbania.World
{
    public class World
    {
        protected int w; // Width, in number of hexagons
        protected int h; // Height, in number of hexagons
        protected int nHexagons; // Number of hexagons
        private Tile[] hexs; // Array containing all hexagonal tiles in this world
        public Vector2 HomePositionWorld { get; private set; }
        public int WidthIndex { get { return w; } }
        public int HeightIndex { get { return h; } }
        public int WidthRender { get { return w * Tile.TranslateWidth; } }
        public int HeightRender { get { return h * Tile.Height; } }

        private UserStats stats = UserStats.GetUserStats();

        public World(int width, int height)
        {
            if (width % 2 != 0) // Ensure width and height are even numbers
            {
                width++;
            }
            if (height % 2 != 0)
            {
                height++;
            }
            w = width;
            h = height;
            nHexagons = w * h;
            hexs = new Tile[nHexagons];
            GenerateTerrain();
        }


        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            for (int i = 0; i < nHexagons; i++)
            {
                hexs[i].Draw(gameTime, spriteBatch);
            }
        }

        public void Update(GameTime gameTime, Utilities.Camera camera)
        {
            Rectangle viewportBounds = camera.GetViewportBounds();
            stats.PropertyValue = 0;
            for (int i = 0; i < nHexagons; i++)
            {
                hexs[i].Update(gameTime, viewportBounds);
                stats.PropertyValue += hexs[i].CostMoney;
            }
        }

        public Tile GetHex(int xIndex, int yIndex)
        {
            int ind = WorldHelper.GetIndexOfHex(xIndex, yIndex, w, h);
            if (ind < 0)
            {
                return null;
            }
            else
            {
                return hexs[ind];
            }
        }

        public double Value()
        {
            double value = 0;
            for (int i = 0; i < nHexagons; i++)
            {
                value += hexs[i].CostMoney;
            }
            return value;
        }

        #region GenerateTerrain

        private void GenerateTerrain()
        {
            Random gen = new Random();
            for (int y = 0; y < h; y++) // Initial instantiation, defaults to { Grass }
            {
                for (int x = 0; x < w; x++)
                {
                    float xpos = x * Tile.TranslateWidth;
                    float ypos;
                    if (x % 2 == 0)
                    {
                        ypos = y * Tile.Height;
                    }
                    else
                    {
                        ypos = (y + 0.5f) * Tile.Height;
                    }
                    hexs[WorldHelper.GetIndexOfHex(x, y, w, h)] = new Grass(x, y, xpos, ypos);
                }
            }
            // Now comes random forest generation
            int rand;
            Tile currentTile;
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    currentTile = GetHex(x, y);
                    rand = gen.Next(100);
                    if (rand < 4)
                    {
                        ChangeTileType(currentTile, TileType.Env_Forest);
                        Tile adjacent;
                        for (int i = 0; i < 6; i++)
                        {
                            rand = gen.Next(100);
                            if (rand < 65)
                            {
                                adjacent = GetAdjacentTile(currentTile, i);
                                rand = gen.Next(100);
                                if (rand < 90 && adjacent != null && adjacent.Type == TileType.Env_Grass)
                                {
                                    ChangeTileType(adjacent, TileType.Env_Forest);
                                }
                            }
                        }
                    }
                }
            }
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    currentTile = GetHex(x, y);
                    rand = gen.Next(100);
                    if (currentTile.Type == TileType.Env_Forest)
                    {
                        Tile adjacent;
                        for (int i = 0; i < 6; i++)
                        {
                            rand = gen.Next(100);
                            if (rand < 5)
                            {
                                adjacent = GetAdjacentTile(currentTile, i);
                                if (adjacent != null && adjacent.Type == TileType.Env_Grass)
                                {
                                    ChangeTileType(adjacent, TileType.Env_Forest);
                                }
                            }
                        }
                    }
                }
            }

            // Water generation
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    currentTile = GetHex(x, y);
                    rand = gen.Next(100);
                    if (rand < 2)
                    {
                        ChangeTileType(currentTile, TileType.Env_Water);
                        Tile adjacent;
                        for (int i = 0; i < 6; i++)
                        {
                            rand = gen.Next(100);
                            if (rand < 45)
                            {
                                adjacent = GetAdjacentTile(currentTile, i);
                                rand = gen.Next(100);
                                if (rand < 55 && adjacent != null && adjacent.Type == TileType.Env_Grass)
                                {
                                    ChangeTileType(adjacent, TileType.Env_Water);
                                }
                            }
                        }
                    }
                }
            }
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    currentTile = GetHex(x, y);
                    rand = gen.Next(100);
                    if (currentTile.Type == TileType.Env_Water)
                    {
                        Tile adjacent;
                        for (int i = 0; i < 6; i++)
                        {
                            rand = gen.Next(100);
                            if (rand < 10)
                            {
                                adjacent = GetAdjacentTile(currentTile, i);
                                if (adjacent != null && adjacent.Type == TileType.Env_Grass)
                                {
                                    ChangeTileType(adjacent, TileType.Env_Water);
                                }
                            }
                        }
                    }
                }
            }
            int xCityHall = gen.Next(3 * w / 7, 5 * w / 7);
            int yCityHall = gen.Next(3 * h / 7, 5 * h / 7);
            Tile cityHall = GetHex(xCityHall, yCityHall);
            ChangeTileType(cityHall, TileType.Buil_CityHall);
            HomePositionWorld = new Vector2(cityHall.PosRender.X, cityHall.PosRender.Y);
        }

#endregion

        #region Adjacency Testing

        // Can return null elements
        public Tile[] GetAllAdjacentTiles(Tile tile)
        {
            Tile[] tiles = new Tile[6];
            for (int i = 0; i < 6; i++)
            {
                tiles[i] = GetAdjacentTile(tile, i);
            }
            return tiles;
        }

        public bool TileIsNextTo(int xIndex, int yIndex, TileType type)
        {
            int numAdj = GetNumberOfAdjacents(xIndex, yIndex, type);
            return (numAdj > 0);
        }

        public bool TileIsNextTo(Vector2 posIndex, TileType type)
        {
            int numAdj = GetNumberOfAdjacents(posIndex, type);
            return (numAdj > 0);
        }

        public bool TileIsNextTo(Tile tile, TileType type)
        {
            int numAdj = GetNumberOfAdjacents(tile, type);
            return (numAdj > 0);
        }

        public int GetNumberOfAdjacents(int xIndex, int yIndex, TileType type)
        {
            int count = 0;
            for (int i = 0; i < 6; i++) // adjacIndex contained in the possible vals of i
            {
                Tile adjacent = GetAdjacentTile(xIndex, yIndex, i);
                if (adjacent.Type == type)
                {
                    count++;
                }
            }
            return count;
        }

        public int GetNumberOfAdjacents(Vector2 posIndex, TileType type)
        {
            return GetNumberOfAdjacents((int)posIndex.X, (int)posIndex.Y, type);
        }

        public int GetNumberOfAdjacents(Tile tile, TileType type)
        {
            if (tile == null)
            {
                return 0;
            }
            return GetNumberOfAdjacents(tile.PosIndex, type);
        }

        // adjacIndex is one of { 0, 1, 2, 3, 4, 5 } which order as 
        //    { up, left-up, left-down, down, right-down, right-up }
        public Tile GetAdjacentTile(int xind, int yind, int adjacIndex)
        {
            Tile toReturn = null;
            Vector2 adjacentIndices = WorldHelper.GetAdjacentTileIndex(xind, yind, adjacIndex);
            int xi = (int)adjacentIndices.X;
            int yi = (int)adjacentIndices.Y;
            toReturn = GetHex(xi, yi);
            //Console.WriteLine("xi: " + xind + "yi: " + yind); // For debugging purposes
            return toReturn;
        }

        // adjacIndex is one of { 0, 1, 2, 3, 4, 5 } which order as 
        //    { up, left-up, left-down, down, right-down, right-up }
        public Tile GetAdjacentTile(Vector2 posIndex, int adjacIndex)
        {
            return GetAdjacentTile((int)posIndex.X, (int)posIndex.Y, adjacIndex);
        }

        // adjacIndex is one of { 0, 1, 2, 3, 4, 5 } which order as 
        //    { up, left-up, left-down, down, right-down, right-up }
        public Tile GetAdjacentTile(Tile tile, int adjacIndex)
        {
            if (tile == null)
            {
                return null;
            }
            return GetAdjacentTile(tile.PosIndex, adjacIndex);
        }

        #endregion

        #region Mouse Picking

        public Vector2 GetTileIndexUnderMouse()
        {
            Vector2 mousePos = Utilities.MouseWrap.PositionWorld;
            float mxScaled = mousePos.X / Tile.TranslateWidth;
            float myScaled = mousePos.Y / Tile.Height;
            int xind = WorldHelper.FloorToInt(mxScaled + 0.5f);
            bool xindIsEven = (xind % 2 == 0);
            int yind;
            if (xindIsEven) // If xind is even
            {
                yind = WorldHelper.FloorToInt(myScaled + 0.5f);
            }
            else // Else xind is odd
            {
                yind = WorldHelper.FloorToInt(myScaled);
            }
            float mxSTrans = mxScaled - xind; // Translated to individual hex origin
            float mySTrans = myScaled - yind; // Translated to individual hex origin, with following correction
            if (!xindIsEven) // If xind is odd, correct for y translation.
            {                               // For the slant lines:
                mySTrans -= 0.5f;           // upper left: y1 = -2 * (x1 + 0.5)
            }                               // lower left: y2 = +2 * (x2 + 0.5)
            if (mySTrans + 2 * mxSTrans + 1 < 0) // If mouse is above the upper left slant side
            {
                if (xindIsEven) // if xind is even
                {
                    yind--;
                }
                xind--;
            }
            else if (mySTrans - 2 * mxSTrans - 1 > 0) // If mouse is below the lower left slant side
            {
                if (!xindIsEven) // If xind is odd
                {
                    yind++;
                }
                xind--;
            }
            return new Vector2(xind, yind);
        }

        public Tile GetTileUnderMouse()
        {
            Vector2 indices = GetTileIndexUnderMouse();
            //Console.WriteLine("xi: " + xind + ", yi: " + yind); // For debugging purposes
            return GetHex((int)indices.X, (int)indices.Y);
        }

        #endregion

        #region Tile Type Changing

        public void UpdateRoadState(Tile tile)
        {
            if (tile != null && tile.Type == TileType.Road)
            {
                ((Road)tile).ClearStateArray();
                Tile adjacent;
                for (int i = 0; i < 6; i++)
                {
                    adjacent = GetAdjacentTile(tile, i);
                    if (adjacent.Type == TileType.Road)
                    {
                        ((Road)tile).AddNextState(i);
                    }
                }
            }
        }

        public void UpdateRoadStateAndAdjacents(Tile tile)
        {
            UpdateRoadState(tile);
            Tile adjacent;
            for (int i = 0; i < 6; i++)
            {
                adjacent = GetAdjacentTile(tile, i);
                UpdateRoadState(adjacent);
            }
        }

        public void ChangeToFactory()
        {
            ChangeTileType(GetTileUnderMouse(), TileType.Buil_Factory);
        }

        public bool ChangeTileType(Tile oldTile, TileType newTileType)
        {
            Tile newTile = null;
            UserStats stats = UserStats.GetUserStats();
            if (oldTile.Type == TileType.Env_Water) return false;
            switch (newTileType)
            {
                case TileType.Road:
                    newTile = new Road(oldTile);
                    break;
                case TileType.Buil_Factory:
                    newTile = new Factory(oldTile);
                    Tile[] neighbors = GetAllAdjacentTiles(oldTile);
                    foreach (Tile n in neighbors)
                    {
                        if (n.Type == TileType.Env_Water)
                        {
                            n.RatePollution += 2;
                            n.RateIncome += 7;
                        }
                        if (n.Type == TileType.Road)
                        {
                            n.RateIncome += 3;
                        }
                        if (n.Type == TileType.Buil_Factory)
                        {
                            n.RateIncome *= 2;
                            n.RatePollution *= 2;

                        }
                    }
                    break;
                case TileType.Buil_PowerPlant:
                    newTile = new PowerPlant(oldTile);
                    break;
                case TileType.Buil_CityHall:
                    newTile = new CityHall(oldTile);
                    break;
                case TileType.Buil_Farm:
                    newTile = new Farm(oldTile);
                    break;
                case TileType.Buil_FireStation:
                    newTile = new FireStation(oldTile);
                    break;
                case TileType.Buil_Harbor:
                    newTile = new Harbor(oldTile);
                    break;
                case TileType.Buil_HomelessShelter:
                    newTile = new HomelessShelter(oldTile);
                    break;
                case TileType.Buil_Hospital:
                    newTile = new Hospital(oldTile);
                    break;
                case TileType.Buil_House:
                    newTile = new House(oldTile);
                    break;
                case TileType.Buil_LargeStore:
                    newTile = new LargeStore(oldTile);
                    break;
                case TileType.Buil_Police:
                    newTile = new Police(oldTile);
                    break;
                case TileType.Buil_School:
                    newTile = new School(oldTile);
                    break;
                case TileType.Buil_SmallStore:
                    newTile = new SmallStore(oldTile);
                    break;
                case TileType.Env_Beach:
                    newTile = new Beach(oldTile);
                    break;
                case TileType.Env_Forest:
                    newTile = new Forest(oldTile);
                    break;
                case TileType.Env_Grass:
                    newTile = new Grass(oldTile);
                    break;
                case TileType.Env_Ice:
                    newTile = new Ice(oldTile);
                    break;
                case TileType.Env_Mountains:
                    newTile = new Mountains(oldTile);
                    break;
                case TileType.Env_Water:
                    newTile = new Water(oldTile);
                    break;
                case TileType.Env_Dirt:
                    newTile = new Dirt(oldTile);
                    break;
                default:
                    newTile = new Dirt(oldTile);
                    break;
            }
            if (stats.Gold - newTile.CostMoney < 0 || stats.BureaucracyPoints - newTile.CostBureauc < 0)
            {
                return false;
            }
            hexs[WorldHelper.GetIndexOfHex(oldTile.PosIndex, w, h)] = newTile;
            stats.Gold -= newTile.CostMoney;
            stats.BureaucracyPoints -= newTile.CostBureauc;
            stats.Food += newTile.RateFood;
            stats.HappinessPer5 += newTile.RateHappiness;
            stats.PollutionPer5 += newTile.RatePollution;
            stats.PollutionPer5 = MathHelper.Clamp((float)stats.PollutionPer5, 0, 100);
            UpdateRoadStateAndAdjacents(newTile);
            return true;
        }

        public void ChangeTileTypeByIndex(int x, int y, int newTileType)
        {
            ChangeTileType(GetHex(x, y), (TileType)newTileType);
        }

        #endregion

    }
}
